package com.hisense.ovcloud.apicontrol.service;

import com.hisense.ovcloud.apicontrol.client.AppServiceClient;
import com.hisense.ovcloud.apicontrol.client.AuthServiceClient;
import com.hisense.ovcloud.apicontrol.config.Constants;
import com.hisense.ovcloud.apicontrol.dto.req.TokenAuthReq;
import com.hisense.ovcloud.apicontrol.dto.req.ValidateReq;
import com.hisense.ovcloud.apicontrol.dto.resp.TokenRes;
import com.hisense.ovcloud.apicontrol.errors.ResForGW;
import com.hisense.ovcloud.apicontrol.errors.SystemError;
import com.hisense.ovcloud.apicontrol.tool.CheckEmptyUtil;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.*;

import static com.hisense.ovcloud.apicontrol.config.Constants.SecurityParamters.SECURITY_START_VER;
import static com.hisense.ovcloud.apicontrol.config.Constants.TOKEN_TYPE_ANONYMOUS;

@Service
@Slf4j
public class AccessTokenCheckService extends BaseAGService {
    private final AuthServiceClient authServiceClient;

    private final AppServiceClient appServiceClient;

    @Value("${anonymous.token.auth.endpoints:/resource/getProtocolInfo,/account/get_pa_tos}")
    private String anonymousTokenAuthEndpoint;
    @Value("${ignore.token.auth.endpoints:/email/stop_rev_msg,/v2/api-docs,/resource/getLoadingScreenInfo,/dms/get_wifi_config,/account/delete_user_data,/account/acc/,/app/get-app-policy-for-himit,/wine-appliance/}")
    private String ignoreTokenAuthEndpoint;

    public AccessTokenCheckService(AuthServiceClient authServiceClient, AppServiceClient appServiceClient) {
        this.authServiceClient = authServiceClient;
        this.appServiceClient = appServiceClient;
    }

    public Mono<ResForGW> run(ValidateReq r, LinkedHashMap<String, Object> parametersMap) {
        ResForGW res = ResForGW.builder().resultCode(Constants.RESULT_CODE_SUCCESS).build();
        if (shouldFilter(r)) {
            if (parametersMap.isEmpty()) {
                return Mono.just(setErrorResponse(res, SystemError.MISSPARAM));
            } else {
                Map<String, Object> insertMaps = new HashMap<>();
                String accessToken = getFromParametersMap(parametersMap,Constants.PARAM_TOKEN);
                String appId = getFromParametersMap(parametersMap,Constants.PARAM_APPID);
                String apiVersion = getFromParametersMap(parametersMap,Constants.VERSION);

                if(!StringUtils.isEmpty(apiVersion) && Double.parseDouble(apiVersion) >= SECURITY_START_VER){
                    if (r.getMethod().equals(Constants.Method.GET)){
                        if(StringUtils.isEmpty(r.getAccessTokenInHeader())){
                            setErrorResponse(res, SystemError.ACCESSTOKEN_NOT_IN_HEADER);
                            return Mono.just(res);
                        }
                        if(!StringUtils.isEmpty(accessToken) && !StringUtils.isEmpty(r.getAccessTokenInHeader())){
                            setErrorResponse(res, SystemError.ACCESSTOKEN_CANNOT_BOTH_IN_HEADER_URL);
                            return Mono.just(res);
                        }
                        accessToken = r.getAccessTokenInHeader();
                    }
                }
                final String accessTokenFinal = accessToken;

                log.info("accessTokenCheckService: appId:{}, apiVersion:{}", appId, apiVersion);


                if (isAnonymousTokenCheck(anonymousTokenAuthEndpoint, r)) {
                    if (StringUtils.isEmpty(apiVersion) || Double.parseDouble(apiVersion) < 2.0) {
                        if (StringUtils.isEmpty(Constants.PARAM_APPID)) {
                            setErrorResponse(res, SystemError.MISSPARAM.getError(Constants.PARAM_APPID));
                            return Mono.just(res);
                        } else {
                            if (CheckEmptyUtil.isEmpty(accessToken)) {
                                setErrorResponse(res, SystemError.PARAMINVALID.getError(Constants.PARAM_APPID));
                                return Mono.just(res);
                            }else{
                                return appServiceClient.getPlatformId(appId)
                                        .zipWhen(platformId -> {
                                            if (platformId == -1L) {
                                                setErrorResponse(res, SystemError.INVALIDAPPID);
                                                return Mono.just(res);
                                            } else {
                                                List<String> platformIdList = new ArrayList<>();
                                                platformIdList.add(platformId + "");
                                                insertMaps.put(Constants.PARAM_PLATFORMID, platformIdList);
                                                List<String> appIdList = new ArrayList<>();
                                                appIdList.add(appId);
                                                insertMaps.put(Constants.PARAM_APPID, appIdList);
                                                res.setData(insertMaps);
                                                return Mono.just(res);
                                            }
                                        }, (x, y) -> y)
                                        .onErrorResume(ex -> {
                                            log.error("accessToken isAnonymousTokenCheck {} failed! res: {}", accessTokenFinal, ex.getMessage());
                                            return Mono.just(setErrorResponse(res, SystemError.MISSPARAM));
                                        });
                            }
                        }
                    } else {
                        if (StringUtils.isEmpty(accessToken)) {
                            setErrorResponse(res, SystemError.PARAMINVALID.getError(Constants.PARAM_TOKEN));
                            return Mono.just(res);
                        } else {
                            return authServiceClient.tokenAuth(TokenAuthReq.builder().accessToken(accessToken).build())
                                    .zipWhen(res1 -> {
                                        if (res1.isSuccess()) {
                                            List<String> platformIdList = new ArrayList<>();
                                            platformIdList.add(res1.getPlatformId() + "");
                                            insertMaps.put(Constants.PARAM_PLATFORMID, platformIdList);
                                            List<String> appIdList = new ArrayList<>();
                                            appIdList.add(appId);
                                            insertMaps.put(Constants.PARAM_APPID, appIdList);
                                            res.setData(insertMaps);

                                        } else {
                                            log.error("anonymousTokenAuthEndpoint ACCESSTOKENERROR for {} via res1 {}", accessTokenFinal, res1);
                                            setErrorResponse(res, SystemError.ACCESSTOKENERROR);
                                        }

                                        return Mono.just(res);
                                    }, (x, y) -> y)
                                    .onErrorResume(ex -> {
                                        log.error("accessToken check {} failed! res: {}", accessTokenFinal, ex.getMessage());
                                        return Mono.just(setErrorResponse(res, SystemError.MISSPARAM));
                                    });
                        }
                    }
                } else {
                    if (StringUtils.isEmpty(Constants.PARAM_TOKEN)) {
                        setErrorResponse(res, SystemError.PARAMINVALID.getError(Constants.PARAM_TOKEN));
                        return Mono.just(res);
                    } else {
                        return authServiceClient.tokenAuth(TokenAuthReq.builder().accessToken(accessToken).build())
                                .zipWhen(res1 -> {
                                    if (res1.isSuccess() && res1.getPlatformId() != -1L && res1.getTokenType() != TOKEN_TYPE_ANONYMOUS) {
                                        if (r.getMethod().equalsIgnoreCase(Constants.Method.GET)) {
                                            List<String> customerIdList = new ArrayList<>();
                                            customerIdList.add(res1.getCustomerId());
                                            insertMaps.put(Constants.PARAM_CUSTOMERID, customerIdList);
                                            List<String> platformIdList = new ArrayList<>();
                                            platformIdList.add(res1.getPlatformId() + "");
                                            insertMaps.put(Constants.PARAM_PLATFORMID, platformIdList);
                                            List<String> appIdList = new ArrayList<>();
                                            appIdList.add(res1.getAppid());
                                            insertMaps.put(Constants.PARAM_APPID, appIdList);
                                            List<String> tenantIdList = new ArrayList<>();
                                            tenantIdList.add(String.valueOf(res1.getTenantId()));
                                            insertMaps.put(Constants.PARAM_TENANTID, tenantIdList);
                                        } else {
                                            insertMaps.put(Constants.PARAM_CUSTOMERID, res1.getCustomerId());
                                            insertMaps.put(Constants.PARAM_PLATFORMID, res1.getPlatformId());
                                            insertMaps.put(Constants.PARAM_APPID, res1.getAppid());
                                            insertMaps.put(Constants.PARAM_TENANTID, res1.getTenantId());
                                        }

                                        res.setData(insertMaps);
                                    } else {
                                        log.error("ACCESSTOKENERROR for {} via res1 {}", accessTokenFinal, res1);
                                        setErrorResponse(res, SystemError.ACCESSTOKENERROR);
                                    }

                                    return Mono.just(res);
                                }, (x, y) -> y);
                    }
                }
            }
        }

        return Mono.just(res);
    }

    public Mono<TokenRes> tokenAuth(String method, String accessToken){
        Map<String, Object> insertMaps = new HashMap<>();
        return authServiceClient.tokenAuth(TokenAuthReq.builder().accessToken(accessToken).build())
                .zipWhen(res1 -> {
                    log.info("accessTokenCheckService: tokenAuth: {}", res1);
                    if (res1.isSuccess() && res1.getPlatformId() != -1L && res1.getTokenType() != TOKEN_TYPE_ANONYMOUS) {
                        if (method.equalsIgnoreCase(Constants.Method.GET)) {
                            List<String> customerIdList = new ArrayList<>();
                            customerIdList.add(res1.getCustomerId());
                            insertMaps.put(Constants.PARAM_CUSTOMERID, customerIdList);
                            List<String> platformIdList = new ArrayList<>();
                            platformIdList.add(res1.getPlatformId() + "");
                            insertMaps.put(Constants.PARAM_PLATFORMID, platformIdList);
                            List<String> appIdList = new ArrayList<>();
                            appIdList.add(res1.getAppid());
                            insertMaps.put(Constants.PARAM_APPID, appIdList);
                            List<String> tenantIdList = new ArrayList<>();
                            tenantIdList.add(String.valueOf(res1.getTenantId()));
                            insertMaps.put(Constants.PARAM_TENANTID, tenantIdList);
                        } else {
                            insertMaps.put(Constants.PARAM_CUSTOMERID, res1.getCustomerId());
                            insertMaps.put(Constants.PARAM_PLATFORMID, res1.getPlatformId());
                            insertMaps.put(Constants.PARAM_APPID, res1.getAppid());
                            insertMaps.put(Constants.PARAM_TENANTID, res1.getTenantId());
                        }

                    }

                    return Mono.just(TokenRes.builder().insertMaps(insertMaps).isValid(true).build());
                }, (x, y) -> y)
                .onErrorReturn(TokenRes.builder().build());
    }

    public boolean shouldFilter(ValidateReq req) {
        return Arrays.stream(ignoreTokenAuthEndpoint.split(",")).noneMatch(ignore -> req.getPath().contains(ignore));
    }

    private boolean isAnonymousTokenCheck(String anonymousTokenAuthEndpoint, ValidateReq req) {
        return Arrays.stream(anonymousTokenAuthEndpoint.split(",")).anyMatch(req.getPath()::contains);
    }
}
